<!--
 * Copyright 2009, Google Inc.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 *     * Redistributions of source code must retain the above copyright
 * notice, this list of conditions and the following disclaimer.
 *     * Redistributions in binary form must reproduce the above
 * copyright notice, this list of conditions and the following disclaimer
 * in the documentation and/or other materials provided with the
 * distribution.
 *     * Neither the name of Google Inc. nor the names of its
 * contributors may be used to endorse or promote products derived from
 * this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
 * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
 * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
 * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
-->
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes">
<title>WebGL Field</title>
<link href="../jquery-ui-1.8.2.custom/css/ui-lightness/jquery-ui-1.8.2.custom.css" rel="stylesheet" />
<link href="../shared/base.css" rel="stylesheet" />
<script src="../jquery-ui-1.8.2.custom/js/jquery-1.4.2.min.js"></script>
<script src="../jquery-ui-1.8.2.custom/js/jquery-ui-1.8.2.custom.min.js"></script>
<script src="../khronos/webgl-debug.js"></script>
<script src="../tdl/base.js"></script>
<script src="../shared/utils.js"></script>
<script>
tdl.require('tdl.buffers');
tdl.require('tdl.fast');
tdl.require('tdl.framebuffers');
tdl.require('tdl.fps');
tdl.require('tdl.log');
tdl.require('tdl.math');
tdl.require('tdl.models');
tdl.require('tdl.particles');
tdl.require('tdl.primitives');
tdl.require('tdl.programs');
tdl.require('tdl.textures');
tdl.require('tdl.webgl');

// globals
var gl;                   // the gl context.
var canvas;               // the canvas
var math;                 // the math lib.
var fast;                 // the fast math lib.
var g_fpsTimer;           // object to measure frames per second;
var g_logGLCalls = true;  // whether or not to log webgl calls
var g_debug = false;      // whether or not to debug.
var g_drawOnce = false;   // draw just one frame.
var g_setCountElements = [];
var g_autoSet = true;
var g_autoSetting = 0;
var g_requestId;

//g_drawOnce = true;
//g_debug = true;

var g_grassSettings = [
  { patchesAcross: 1 },
  { patchesAcross: 4 },
  { patchesAcross: 6 },
  { patchesAcross: 10 },
  { patchesAcross: 31 }
];

var g = {
  grassConst: {},
  dofConst: {},
  globals: {
    showRT: 0
  }
};

var g_ui = [
  { obj: 'grassConst', name: 'bladeSpacing',     value: 0.06,  max:  0.5 },
  { obj: 'grassConst', name: 'bladeWidth',       value: 0.041, max:  0.5 },
  { obj: 'grassConst', name: 'topWidth',         value: 0.007, max:  0.5 },
  { obj: 'grassConst', name: 'heightRange',      value: 0.1,   max:  0.5 },
  { obj: 'grassConst', name: 'xWorldMult',       value: 2.45,  max:  5 },
  { obj: 'grassConst', name: 'zWorldMult',       value: 4.95,  max:  5 },
  { obj: 'grassConst', name: 'xTimeMult',        value: 2.07,  max:  5 },
  { obj: 'grassConst', name: 'zTimeMult',        value: 0.94,  max:  5 },
  { obj: 'grassConst', name: 'swayRange',        value: 0.09,  max:  1 },
  { obj: 'grassConst', name: 'elevationPeriod1', value: 2.37,  max:  5 },
  { obj: 'grassConst', name: 'elevationPeriod2', value: 1.63,  max:  5 },
  { obj: 'grassConst', name: 'elevationPointX',  value: 8.06,  max:  10 },
  { obj: 'grassConst', name: 'elevationPointZ',  value: 5.02,  max:  10 },
  { obj: 'grassConst', name: 'elevationRange',   value: 0.2,   max:  3 },
  { obj: 'grassConst', name: 'rand1Mult',        value: 0.37,  max:  5 },
  { obj: 'grassConst', name: 'rand2Mult',        value: 2.89,  max:  5 },
  { obj: 'globals',    name: 'targetHeight',     value: 0.91,  max:  5 },
  { obj: 'globals',    name: 'targetRadius',     value: 1,     max:  5 },
  { obj: 'globals',    name: 'eyeHeight',        value: 2.98,  max:  10 },
  { obj: 'globals',    name: 'eyeRadius',        value: 5,     max:  20 },
  { obj: 'globals',    name: 'eyeSpeed',         value: 0.1,   max:  1 },
  { obj: 'globals',    name: 'patchesAcross',    value: 3,     max:  31 },
  { obj: 'dofConst',   name: 'dof',              value: 0.79,  max:  1 },
  { obj: 'dofConst',   name: 'dofInnerRange',    value: 0.034, max:  1 },
  { obj: 'dofConst',   name: 'dofOuterRange',    value: 0.078, max:  1 },
  ];

function ValidateNoneOfTheArgsAreUndefined(functionName, args) {
  for (var ii = 0; ii < args.length; ++ii) {
    if (args[ii] === undefined) {
      tdl.error("undefined passed to gl." + functionName + "(" +
          tdl.webgl.glFunctionArgsToString(functionName, args) + ")");
    }
  }
}

function Log(msg) {
  if (g_logGLCalls) {
    tdl.log(msg);
  }
}

function LogGLCall(functionName, args) {
  if (g_logGLCalls) {
    ValidateNoneOfTheArgsAreUndefined(functionName, args)
    tdl.log("gl." + functionName + "(" +
            tdl.webgl.glFunctionArgsToString(functionName, args) + ")");
  }
}

/**
 * Sets up Grass.
 */
function setupGrass() {
  var textures = { };
  var program = tdl.programs.loadProgramFromScriptTags(
      'grassVertexShader',
      'grassFragmentShader');
  var arrays = tdl.primitives.createPlane(1, 1, 1, 10);
  tdl.primitives.reorient(arrays,
      [1, 0, 0, 0,
       0, 0, -1, 0,
       0, 1, 0, 0,
       0, 0.5, 0, 1]);
  delete arrays.normal;
  delete arrays.texCoord;
  var numElements = arrays.position.numElements;
  var blades = [arrays]
  var across = 10;
  for (var xx = 1; xx < across; ++xx) {
    var a2 = tdl.primitives.clone(arrays);
    blades[xx] = a2;
  }
  var arrays = tdl.primitives.concat(blades);
  var blades = [arrays]
  for (var zz = 1; zz < across; ++zz) {
    var a2 = tdl.primitives.clone(arrays);
    blades[zz] = a2;
  }
  var arrays = tdl.primitives.concat(blades);
  var numBlades = across * across;
  var bladeId = new tdl.primitives.AttribBuffer(
      4, numElements * numBlades);
  for (var xx = 0; xx < across; ++xx) {
    for (var zz = 0; zz < across; ++zz) {
      var r1 = Math.random();
      var r2 = Math.random();
      for (var jj = 0; jj < numElements; ++jj) {
        bladeId.push([xx, zz, r1, r2]);
      }
    }
  }
  arrays.bladeId = bladeId;
  return new tdl.models.Model(program, arrays, textures);
}

/**
 * Sets up dofplane.
 */
function setupDofPlane(mainFBO, blurTexture) {
  var textures = {
    mainSampler: mainFBO.texture,
    blurSampler: blurTexture
  };
  var fshader = 'dofFragmentShaderWithAlphaDepth';
  if (mainFBO.depthTexture) {
    textures.depthSampler = mainFBO.depthTexture;
    fshader = 'dofFragmentShaderWithDepthTexture';
    tdl.log("using depth texture");
  }
  var program = tdl.programs.loadProgramFromScriptTags(
      'dofVertexShader',
      fshader);
  var arrays = tdl.primitives.createPlane(2, 2, 1, 1);
  tdl.primitives.reorient(arrays,
      [1, 0, 0, 0,
       0, 0,-1, 0,
       0, 1, 0, 0,
       0, 0, 0, 1]);
  delete arrays.normal;
  return new tdl.models.Model(program, arrays, textures);
}

/**
 * Sets up blur plane.
 */
function setupBlurPlane(mainTexture) {
  var textures = {mainSampler: mainTexture};
  var program = tdl.programs.loadProgramFromScriptTags(
      'blurVertexShader',
      'blurFragmentShader');
  var arrays = tdl.primitives.createPlane(2, 2, 1, 1);
  tdl.primitives.reorient(arrays,
      [1, 0, 0, 0,
       0, 0,-1, 0,
       0, 1, 0, 0,
       0, 0, 0, 1]);
  delete arrays.normal;
  return new tdl.models.Model(program, arrays, textures);
}

/**
 * Sets up RT plane.
 */
function setupRTPlane() {
  var textures = {};
  var program = tdl.programs.loadProgramFromScriptTags(
      'rtVertexShader',
      'rtFragmentShader');
  var arrays = tdl.primitives.createPlane(1, 1, 1, 1);
  tdl.primitives.reorient(arrays,
      [1, 0, 0, 0,
       0, 0,-1, 0,
       0, 1, 0, 0,
       0, 0, 0, 1]);
  delete arrays.normal;
  return new tdl.models.Model(program, arrays, textures);
}

/**
 * Sets up Skybox.
 */
function setupSkybox() {
  var textures = {
    skybox: tdl.textures.loadTexture([
        'assets/mid.png',
        'assets/mid.png',
        'assets/top.png',
        'assets/bottom.png',
        'assets/mid.png',
        'assets/mid.png'])
  };
  var program = tdl.programs.loadProgramFromScriptTags(
      'skyboxVertexShader',
      'skyboxFragmentShader');
  var arrays = tdl.primitives.createPlane(2, 2, 1, 1);
  delete arrays['normal'];
  delete arrays['texCoord'];
  tdl.primitives.reorient(arrays,
      [1, 0, 0, 0,
       0, 0, 1, 0,
       0,-1, 0, 0,
       0, 0, 0.99, 1]);
  return new tdl.models.Model(program, arrays, textures);
}

/**
 * Sets the count
 */
function setCount(elem, settings) {
  g_autoSet = false;
  for (var name in settings) {
    g.globals[name] = settings[name];
  }
  for (var ii = 0; ii < g_setCountElements.length; ++ii) {
    g_setCountElements[ii].style.color = "black";
  }
  elem.style.color = "red";
}

/**
 * Sets up the count buttons.
 */
function setupCountButtons() {
  for (var ii = 0; ii < g_grassSettings.length; ++ii) {
    var elem = document.getElementById("setCount" + ii);
    if (!elem) {
      break;
    }
    g_setCountElements.push(elem);
    elem.onclick = function(elem, settings) {
      return function () {
        setCount(elem, settings);
      }
    }(elem, g_grassSettings[ii]);
  }
  setCount(document.getElementById('setCount0'), g_grassSettings[0]);
  g_autoSet = true;
  $('#advanced').click(function() {
      g_autoSet = false;
      $("#uiContainer").toggle('slow'); return false; });
  $("#uiContainer").toggle();

}

function main() {
  math = tdl.math;
  fast = tdl.fast;
  canvas = document.getElementById("canvas");
  g_fpsTimer = new tdl.fps.FPSTimer();

  setupCountButtons();

  //canvas = WebGLDebugUtils.makeLostContextSimulatingCanvas(canvas);
  // tell the simulator when to lose context.
  //canvas.loseContextInNCalls(1);

  tdl.webgl.registerContextLostHandler(canvas, handleContextLost);
  tdl.webgl.registerContextRestoredHandler(canvas, handleContextRestored);

  gl = tdl.webgl.setupWebGL(canvas);
  if (!gl) {
    return false;
  }
  if (g_debug) {
    gl = tdl.webgl.makeDebugContext(gl, undefined, LogGLCall);
  }

  initialize();
}

function handleContextLost() {
  tdl.log("context lost");
  cancelAnimationFrame(g_requestId);
}

function handleContextRestored() {
  tdl.log("context restored");
  initialize();
}

function initialize() {
 mainFBO = tdl.framebuffers.createFramebuffer(canvas.width, canvas.height, true);
  var horizontalBlurFBO = tdl.framebuffers.createFramebuffer(canvas.width / 4, canvas.height);
  var verticalBlurFBO = tdl.framebuffers.createFramebuffer(canvas.width / 4, canvas.height / 4);
  Log("--Setup Grass---------------------------------------");
  var grass = setupGrass();
  Log("--Setup Skybox---------------------------------------");
  var skybox = setupSkybox();
  Log("--Setup HorizontalBlurPlane---------------------------------------");
  var horizontalBlurPlane = setupBlurPlane(mainFBO.texture);
  Log("--Setup VerticalBlurPlane---------------------------------------");
  var verticalBlurPlane = setupBlurPlane(horizontalBlurFBO.texture);
  Log("--Setup DofPlane---------------------------------------");
  var dofPlane = setupDofPlane(mainFBO, verticalBlurFBO.texture);
  Log("--Setup RTPlane---------------------------------------");
  var rtPlane = setupRTPlane();

  var then = 0.0;
  var clock = 0.0;
  var fpsElem = document.getElementById("fps");

  var projection = new Float32Array(16);
  var view = new Float32Array(16);
  var identity = new Float32Array([1,0,0,0,0,1,0,0,0,0,1,0,0,0,0,1]);
  var world = new Float32Array(16);
  var worldInverse = new Float32Array(16);
  var worldInverseTranspose = new Float32Array(16);
  var viewProjection = new Float32Array(16);
  var worldViewProjection = new Float32Array(16);
  var viewInverse = new Float32Array(16);
  var viewDirectionProjectionInverse = new Float32Array(16);
  var eyePosition = new Float32Array(3);
  var target = new Float32Array(3);
  var up = new Float32Array([0,1,0]);
  var lightWorldPos = new Float32Array(3);
  var v3t0 = new Float32Array(3);
  var v3t1 = new Float32Array(3);
  var v3t2 = new Float32Array(3);
  var v3t3 = new Float32Array(3);
  var m4t0 = new Float32Array(16);
  var m4t1 = new Float32Array(16);
  var m4t2 = new Float32Array(16);
  var m4t3 = new Float32Array(16);
  var zero4 = new Float32Array(4);
  var one4 = new Float32Array([1,1,1,1]);

  // Sky uniforms.
  var skyConst = {viewDirectionProjectionInverse: viewDirectionProjectionInverse};
  var skyPer = {};

  // dofPlane uniforms.
  var dofPer = {};

  // blurPlane uniforms.
  var horizontalBlurConst = {
      blurSize: new Float32Array([2.0 / canvas.width, 0])};
  var verticalBlurConst = {
      blurSize: new Float32Array([0, 2.0 / canvas.height])};
  var blurPer = {};

  // Grass uniforms.
  g.grassConst.viewInverse = viewInverse;
  g.grassConst.time = 0;

  var grassPer = {
    world: world,
    worldViewProjection: worldViewProjection,
    worldInverse: worldInverse,
    worldInverseTranspose: worldInverseTranspose};

  var frameCount = 0;
  var eyeClock = 0;
  var countAdjustClock = 0;

  function render() {
    ++frameCount;
    var now = (new Date()).getTime() * 0.001;
    var elapsedTime;
    if(then == 0.0) {
      elapsedTime = 0.0;
    } else {
      elapsedTime = now - then;
    }
    then = now;

    g_fpsTimer.update(elapsedTime);
    fpsElem.innerHTML = g_fpsTimer.averageFPS;

    if (g_autoSet) {
      countAdjustClock += elapsedTime;
      if (countAdjustClock > 0.5) {
        if (countAdjustClock > 1.5) {
          g_autoSet = false;
        }
        if (g_fpsTimer.averageFPS > 17) {
          ++g_autoSetting;
          if (g_autoSetting < g_grassSettings.length - 1)
          {
            setCount(g_setCountElements[g_autoSetting],
                     g_grassSettings[g_autoSetting]);
            g_autoSet = true;
            var lerp = Math.min(4, Math.max(0, g.globals.patchesAcross - 3)) / 4;
            g.dofConst.dof = math.lerpScalar(0.742, 0.79, lerp);
            g.globals.eyeHeight = math.lerpScalar(2.62, 2.98, lerp);
            g.globals.eyeRadius = math.lerpScalar(3.56, 5, lerp);
            g.globals.targetHeight = math.lerpScalar(0.1, 0.91, lerp);
            countAdjustClock = 0;
          }
        }
        if (g_autoSetting >= g_grassSettings.length - 1) {
          g_autoSet = false;
        }
      }
    }

    clock += elapsedTime;
    eyeClock += elapsedTime * g.globals.eyeSpeed;
    eyePosition[0] = Math.sin(eyeClock) * g.globals.eyeRadius;
    eyePosition[1] = g.globals.eyeHeight;
    eyePosition[2] = Math.cos(eyeClock) * g.globals.eyeRadius;
    target[0] = Math.sin(eyeClock + Math.PI) * g.globals.targetRadius;
    target[1] = g.globals.targetHeight;
    target[2] = Math.cos(eyeClock + Math.PI) * g.globals.targetRadius;

    // Draw to main fbo.
    mainFBO.bind();

    gl.colorMask(true, true, true, true);
    gl.clearColor(0,0,0,0);
    gl.clearDepth(1);
    gl.clear(gl.COLOR_BUFFER_BIT | gl.DEPTH_BUFFER_BIT | gl.STENCIL_BUFFER_BIT);

    gl.disable(gl.BLEND);
    gl.depthFunc(gl.LESS);
    gl.disable(gl.CULL_FACE);
    gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);

    fast.matrix4.perspective(
        projection,
        math.degToRad(30),
        canvas.clientWidth / canvas.clientHeight,
        1,
        20);
    fast.matrix4.lookAt(
        view,
        eyePosition,
        target,
        up);
    fast.matrix4.mul(viewProjection, view, projection);
    fast.matrix4.inverse(viewInverse, view);
    fast.matrix4.copy(m4t0, view);
    fast.matrix4.setTranslation(m4t0, [0, 0, 0]);
    fast.matrix4.mul(m4t1, m4t0, projection);
    fast.matrix4.inverse(viewDirectionProjectionInverse, m4t1);

    fast.matrix4.getAxis(v3t0, viewInverse, 0); // x
    fast.matrix4.getAxis(v3t1, viewInverse, 1); // y;
    fast.matrix4.getAxis(v3t2, viewInverse, 2); // z;
    fast.mulScalarVector(v3t0, 1, v3t0);
    fast.mulScalarVector(v3t1, 1, v3t1);
    fast.mulScalarVector(v3t2, 1, v3t2);
    fast.addVector(lightWorldPos, eyePosition, v3t0);
    fast.addVector(lightWorldPos, lightWorldPos, v3t1);
    fast.addVector(lightWorldPos, lightWorldPos, v3t2);
//      view: view,
//      projection: projection,
//      viewProjection: viewProjection,

    // Draw Skybox
    gl.disable(gl.DEPTH_TEST);
    gl.depthMask(false);

    skybox.drawPrep(skyConst);
    skybox.draw(skyPer);

    gl.enable(gl.DEPTH_TEST);
    gl.depthMask(true);

    Log("--Draw grass---------------------------------------");
    g.grassConst.time = clock;
    grass.drawPrep(g.grassConst);
    var across = g.globals.patchesAcross;
    var width = g.grassConst.bladeSpacing * 10;
    var base = width * across * -0.5;
    for (var zz = 0; zz < across; ++zz) {
      for (var xx = 0; xx < across; ++xx) {
        fast.matrix4.translation(world, [base + xx * width, 0, base + zz * width]),
        fast.matrix4.mul(worldViewProjection, world, viewProjection);
        fast.matrix4.inverse(worldInverse, world);
        fast.matrix4.transpose(worldInverseTranspose, worldInverse);
        grass.draw(grassPer);
      }
    }

    // Draw to horizontal blur fbo.
    horizontalBlurFBO.bind();
    gl.disable(gl.BLEND);
    gl.disable(gl.DEPTH_TEST);

    // Draw HorizontalBlurPlane
    horizontalBlurPlane.drawPrep(horizontalBlurConst);
    horizontalBlurPlane.draw(blurPer);

    // Draw to vertical blur fbo.
    verticalBlurFBO.bind();
    gl.disable(gl.BLEND);
    gl.disable(gl.DEPTH_TEST);

    // Draw VerticalBlurPlane
    verticalBlurPlane.drawPrep(verticalBlurConst);
    verticalBlurPlane.draw(blurPer);

    // Draw to backbuffer.
    verticalBlurFBO.unbind();
    gl.colorMask(true, true, true, true);
    gl.depthMask(true);
    gl.clearColor(0,0,1,1);
    gl.clear(gl.COLOR_BUFFER_BIT);
    gl.disable(gl.BLEND);
    gl.disable(gl.DEPTH_TEST);

    // Draw DofPlane
    dofPlane.drawPrep(g.dofConst);
    dofPlane.draw(dofPer);

    function drawPlane(texture, mixAmount, worldViewProjection) {
      var mixAmount;
      var texture;
      rtPlane.draw({
          worldViewProjection: worldViewProjection,
          mixAmount: mixAmount
        }, {
          texture: texture
        });
    }

    function drawRT(rtId, projection) {
      switch (rtId) {
      case 1: // main FBO color
        mixAmount = 0;
        texture = mainFBO.texture;
        break;
      case 2: // blur
        mixAmount = 0;
        texture = verticalBlurFBO.texture;
        break;
      case 3: // depth (alpha or depth)
        mixAmount = 1;
        texture = mainFBO.texture;
        if (mainFBO.depthTexture) {
          mixAmount = 0;
          texture = mainFBO.depthTexture;
        }
        break;
      case 4: // alpha
        mixAmount = 1;
        texture = mainFBO.texture;
        break;
      }
      drawPlane(texture, mixAmount, projection);
    }

    if (g.globals.showRT) {
      rtPlane.drawPrep();
      if (g.globals.showRT == 4) {
        drawRT(1, [0.5,0,0,0,0,0.5,0,0,0,0,1,0,-0.6,0,0,1]);
        drawRT(2, [0.5,0,0,0,0,0.5,0,0,0,0,1,0,-0.0,0,0,1]);
        drawRT(3, [0.5,0,0,0,0,0.5,0,0,0,0,1,0,0.6,0,0,1]);
        // drawRT(4, [0.5,0,0,0,0,0.5,0,0,0,0,1,0,-0.0,0.6,0,1]);
      } else {
        drawRT(g.globals.showRT, [2,0,0,0,0,2,0,0,0,0,1,0,0,0,0,1]);
      }
    }

    // Set the alpha to 255.
    gl.colorMask(false, false, false, true);
    gl.clearColor(0,0,0,1);
    gl.clear(gl.COLOR_BUFFER_BIT);

    // turn off logging after 1 frame.
    g_logGLCalls = false;

    if (!g_drawOnce) {
      g_requestId = requestAnimationFrame(render);
    }
  }
  render();
  return true;
}

var g_event;
var g_ui;

function getParamId(id) {
  return id.substr(6).replace(/(\w)/, function(m) {return m.toLowerCase() });
}

function setParam(event, ui, obj, valueElem) {
  var id = event.target.id;
  var value = ui.value / 1000;
  valueElem.innerHTML = value;
  obj[id] = value;
}

function getUIValue(obj, id) {
  return obj[id] * 1000;
}

function setupSlider($, elem, ui, obj) {
  var textDiv = document.createElement('div');
  var labelDiv = document.createElement('span');
  labelDiv.appendChild(document.createTextNode(ui.name));
  var valueDiv = document.createElement('span');
  valueDiv.appendChild(
      document.createTextNode(getUIValue(obj, ui.name) / 1000));
  valueDiv.style.position = "absolute";
  valueDiv.style.right = "10px";
  var sliderDiv = document.createElement('div');
  sliderDiv.id = ui.name;
  textDiv.appendChild(labelDiv);
  textDiv.appendChild(valueDiv);
  elem.appendChild(textDiv);
  elem.appendChild(sliderDiv);
  $(sliderDiv).slider({
    range: false,
    step: 1,
    max: ui.max * 1000,
    value: getUIValue(obj, ui.name),
    slide: function(event, ui) { setParam(event, ui, obj, valueDiv); }
  });
}

$(function(){
  setupFullscreen("fullscreen", "body");
  var uiElem = document.getElementById('ui');
  for (var ii = 0; ii < g_ui.length; ++ii) {
    var ui = g_ui[ii];
    var obj = g[ui.obj];
    obj[ui.name] = ui.value;
    var div = document.createElement('div');
    setupSlider($, div, ui, obj);
    uiElem.appendChild(div);
  }
  $(document).keypress(function(event) {
    if (event.which == 'd'.charCodeAt(0) ||
        event.which == 'D'.charCodeAt(0)) {
      g.globals.showRT = (g.globals.showRT + 1) % 5;
    }
  });

  main();
});


</script>
</head>
<body>
<div id="info"><a href="http://threedlibrary.googlecode.com" target="_blank">tdl.js</a> - field</div>
<div id="fpsContainer">
  <div class="fpsInner">
    <div id="fullscreen"></div>
    <div class="fps">fps: <span id="fps"></span></div>
    <div class="clear"></div>
  </div>
  <div>Grass</div>
  <div class="clickable" id="setCount0">1</div>
  <div class="clickable" id="setCount1">a few</div>
  <div class="clickable" id="setCount2">some</div>
  <div class="clickable" id="setCount3">many</div>
  <div>
  <span  class="clickable" id="setCount4">lots</span>
  <span style="float: right" class="clickable" id="advanced">*</span>
  </div>
</div>
<div id="uiContainer">
<div id="ui"></div>
</div>
<div id="viewContainer">
<canvas id="canvas" width="1024" height="1024" style="width: 100%; height: 100%;"></canvas>
</div>
</body>
<script id="rtVertexShader" type="text/something-not-javascript">
attribute vec4 position;
attribute vec2 texCoord;
uniform mat4 worldViewProjection;
varying vec2 v_texCoord;
void main() {
  gl_Position = worldViewProjection * position;
  v_texCoord = texCoord;
}
</script>
<script id="rtFragmentShader" type="text/something-not-javascript">
precision mediump float;
varying vec2 v_texCoord;
uniform float mixAmount;
uniform sampler2D texture;
void main() {
  vec4 color = texture2D(texture, v_texCoord);
  gl_FragColor = vec4(mix(color.rgb, color.aaa, mixAmount), 1);
}
</script>
<script id="grassVertexShader" type="text/something-not-javascript">
uniform mat4 worldViewProjection;
uniform mat4 world;
uniform mat4 viewInverse;
uniform float time;
uniform float bladeWidth;
uniform float topWidth;
uniform float bladeSpacing;
uniform float heightRange;
uniform float horizontalRange;
uniform float xWorldMult;
uniform float zWorldMult;
uniform float xTimeMult;
uniform float zTimeMult;
uniform float rand1Mult;
uniform float rand2Mult;
uniform float swayRange;
uniform float elevationPeriod1;
uniform float elevationPeriod2;
uniform float elevationPointX;
uniform float elevationPointZ;
uniform float elevationRange;
attribute vec4 position;
attribute vec4 bladeId;
varying vec4 v_color;

vec4 brightColor = vec4(175.0 / 255.0, 218.0 / 255.0, 44.0 / 255.0, 1);
vec4 darkColor = vec4(53.0 / 255.0, 85.0 / 255.0, 7.0 / 255.0, 1);
vec4 darkerColor = vec4(53.0 / 255.0, 85.0 / 255.0, 7.0 / 255.0, 1) * 0.5;

float circleWave(vec4 center, vec4 position, float period) {
  vec4 diff = position - center;
  float dist = sqrt(diff.x * diff.x + diff.z * diff.z);
  return sin(dist * period);
}

void main() {
  float height = position.y;
  float lerp = height;

  mat4 vm = mat4(
      viewInverse[0],
      viewInverse[1],
      viewInverse[2],
     vec4(0,0,0,1));
  float width = mix(bladeWidth, topWidth, lerp);
  vec4 pt = vm * vec4(
      position.x * width,
      0,
      position.z * width,
      1) +
      vec4(0, position.y, 0, 0);

  float xbase = bladeId.x;
  float zbase = bladeId.y;
  float rand1 = bladeId.z;
  float rand2 = bladeId.w;
  float hbase = time * sin((xbase + zbase) * 0.1);
  float hsin  = sin(hbase);
  float hcos  = cos(hbase);
  float worldOff = world[3][0] * xWorldMult * world[3][2] * zWorldMult;
  float xoff  = sin(time * xTimeMult + rand1 + worldOff + xbase * 0.3 + sin(zbase)) * swayRange;
  float yoff  = 0.0;
  float zoff  = sin(time * zTimeMult + rand2 + worldOff + zbase * 0.1 + cos(xbase) * 1.3) * swayRange;
  float effect = 1.0 - cos(3.14159 * 0.5 * lerp) * 1.0;
  vec4 p = vec4(
      pt.x + xbase * bladeSpacing + xoff * effect + rand1 * bladeSpacing,
      pt.y                        + yoff          + rand1 * heightRange,
      pt.z + zbase * bladeSpacing + zoff * effect + rand1 * bladeSpacing,
      1);

  vec4 wp = world * p;

  rand1 = mod(rand1 + wp.x * rand1Mult + wp.z * rand2Mult, 1.0);
  rand2 = mod(rand2 + wp.z * rand1Mult + wp.x * rand2Mult, 1.0);
  float elevationBasis1 = circleWave(vec4(0,0,0,0), wp, elevationPeriod1);
  float elevationBasis2 = circleWave(vec4(elevationPointX, 0, elevationPointZ, 0), wp, elevationPeriod2);
  vec4 nextP = vec4(wp.x + bladeSpacing, wp.y, wp.z + bladeSpacing, 1);
  float elevationBasis3 = circleWave(vec4(0,0,0,0), nextP, elevationPeriod1);
  float elevationBasis4 = circleWave(vec4(elevationPointX, 0, elevationPointZ, 0), nextP, elevationPeriod2);
  float elevationBasis = elevationBasis2 - elevationBasis1;
  float elevationBasisNext = elevationBasis4 - elevationBasis3;

  vec4 clipPosition = worldViewProjection * vec4(
      p.x,
      p.y + elevationBasis * elevationRange,
      p.z,
      1);
  gl_Position = clipPosition;

  vec4 color = mix(darkColor, brightColor, lerp);
  vec4 randColor = vec4(rand2 * 0.2, rand2 * 0.2 ,rand2 * 0.2, 0);
  float l = dot(vec3(0.18814417367671946, 0.9407208683835973, 0.28221626051507914),
                normalize(vec3(0.06, elevationBasis * elevationRange - elevationBasisNext * elevationRange, 0.06)));

  // encode the depth mix amount into the alpha channel
  float depthMix = (clipPosition.z / clipPosition.w + 1.0) * 0.5;
  v_color = vec4((randColor + mix(color, darkerColor, l)).xyz, depthMix);
}

</script>
<script id="grassFragmentShader" type="text/something-not-javascript">
precision mediump float;
varying vec4 v_color;

void main() {
  gl_FragColor = v_color;
}
</script>
<!-- ===[ blur ]============================================== -->
<script id="blurVertexShader" type="text/something-not-javascript">
attribute vec4 position;
attribute vec2 texCoord;
varying vec4 v_position;
varying vec2 v_texCoord;
void main() {
  v_texCoord = texCoord;
  gl_Position = position;
}
</script>
<script id="blurFragmentShader" type="text/something-not-javascript">
precision mediump float;
varying vec2 v_texCoord;
uniform vec2 blurSize;
uniform sampler2D mainSampler;
void main() {
   vec4 sum = vec4(0.0);

   sum += texture2D(mainSampler, vec2(v_texCoord.x - 4.0 * blurSize.x, v_texCoord.y - 4.0 * blurSize.y)) * 0.05;
   sum += texture2D(mainSampler, vec2(v_texCoord.x - 3.0 * blurSize.x, v_texCoord.y - 3.0 * blurSize.y)) * 0.09;
   sum += texture2D(mainSampler, vec2(v_texCoord.x - 2.0 * blurSize.x, v_texCoord.y - 2.0 * blurSize.y)) * 0.12;
   sum += texture2D(mainSampler, vec2(v_texCoord.x - 1.0 * blurSize.x, v_texCoord.y - 1.0 * blurSize.y)) * 0.15;
   sum += texture2D(mainSampler, vec2(v_texCoord.x                   , v_texCoord.y                   )) * 0.16;
   sum += texture2D(mainSampler, vec2(v_texCoord.x + 1.0 * blurSize.x, v_texCoord.y + 1.0 * blurSize.y)) * 0.15;
   sum += texture2D(mainSampler, vec2(v_texCoord.x + 2.0 * blurSize.x, v_texCoord.y + 2.0 * blurSize.y)) * 0.12;
   sum += texture2D(mainSampler, vec2(v_texCoord.x + 3.0 * blurSize.x, v_texCoord.y + 3.0 * blurSize.y)) * 0.09;
   sum += texture2D(mainSampler, vec2(v_texCoord.x + 4.0 * blurSize.x, v_texCoord.y + 4.0 * blurSize.y)) * 0.05;

   gl_FragColor = sum;
}
</script>
<!-- ===[ dof ]============================================== -->
<script id="dofVertexShader" type="text/something-not-javascript">
attribute vec4 position;
attribute vec2 texCoord;
varying vec4 v_position;
varying vec2 v_texCoord;
void main() {
  v_texCoord = texCoord;
  gl_Position = position;
}
</script>
<script id="dofFragmentShaderWithAlphaDepth" type="text/something-not-javascript">
precision mediump float;
varying vec2 v_texCoord;
uniform sampler2D mainSampler;
uniform sampler2D blurSampler;
uniform float dof;
uniform float dofInnerRange;
uniform float dofOuterRange;
void main() {
  vec4 mainColor = texture2D(mainSampler, v_texCoord);
  vec4 blurColor = texture2D(blurSampler, v_texCoord);
  float depth = mainColor.a;
  float mix_ = (abs(dof - depth) - dofInnerRange);
  float mixAmount = 0.0;
  float blurRange = dofOuterRange - dofInnerRange;
  if (mix_ > blurRange) {
    mixAmount = 1.0;
  } else {
    mixAmount = mix_ / blurRange;
  }
  vec4 color = mix(mainColor, blurColor, mixAmount);
  // Uncomment next line to show depth.
  //color = color * 0.0001 + vec4(mainColor.aaa, 1) * 0.9999;
  // Uncomment next line to show mix amount/
  //color = color * 0.0001 + vec4(mixAmount, mixAmount, mixAmount, 1);
  gl_FragColor = color;
}
</script>
<script id="dofFragmentShaderWithDepthTexture" type="text/something-not-javascript">
precision mediump float;
varying vec2 v_texCoord;
uniform sampler2D mainSampler;
uniform sampler2D blurSampler;
uniform sampler2D depthSampler;
uniform float dof;
uniform float dofInnerRange;
uniform float dofOuterRange;
void main() {
  vec4 mainColor = texture2D(mainSampler, v_texCoord);
  vec4 blurColor = texture2D(blurSampler, v_texCoord);
  float depth = texture2D(depthSampler, v_texCoord).r;
  float mix_ = max(0.0, (abs(dof - depth) - dofInnerRange));
  float mixAmount = 0.0;
  float blurRange = dofOuterRange - dofInnerRange;
  if (mix_ > blurRange) {
    mixAmount = 1.0;
  } else {
    mixAmount = mix_ / blurRange;
  }
  vec4 color = mix(mainColor, blurColor, mixAmount);
  // Uncomment next line to show depth.
  //color = color * 0.0001 + vec4(depth, depth, depth, 1) * 0.9999;
  // Uncomment next line to show mix amount/
  //color = color * 0.0001 + vec4(mixAmount, mixAmount, mixAmount, 1);
  gl_FragColor = color;
}
</script>
<!-- ===[ SkyBox ]============================================== -->
<script id="skyboxVertexShader" type="text/something-not-javascript">
attribute vec4 position;
varying vec4 v_position;
void main() {
  v_position = position;
  gl_Position = position;
}
</script>
<script id="skyboxFragmentShader" type="text/something-not-javascript">
precision mediump float;
uniform samplerCube skybox;
uniform mat4 viewDirectionProjectionInverse;
varying vec4 v_position;
void main() {
  vec4 t = (viewDirectionProjectionInverse * v_position);
  vec4 color = textureCube(
      skybox,
      normalize(t.xyz / t.w));
  gl_FragColor = color;
}
</script>
</html>


